package com.eight.cloud.openapi.aspect;

import cn.hutool.core.util.ObjectUtil;
import com.eight.cloud.core.global.model.AppUser;
import com.eight.cloud.core.global.result.Result;
import com.eight.cloud.core.utils.HttpUtils.HttpContextUtils;
import com.eight.cloud.core.utils.format.ObjectFormat;
import com.eight.cloud.core.utils.quick.RSAUtils;
import com.eight.cloud.core.utils.supply.UserContext;
import com.eight.cloud.openapi.annotations.OpenApi;
import com.eight.cloud.openapi.common.CacheKeyConstant;
import com.eight.cloud.openapi.interfaces.AuthDetailService;
import com.eight.cloud.openapi.interfaces.AuthDetails;
import com.eight.cloud.openapi.interfaces.LogRecordService;
import com.eight.cloud.openapi.util.SignatureUtil;
import com.eight.cloud.redisson.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.bind.annotation.RequestBody;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ValidationException;
import java.lang.annotation.Annotation;
import java.util.SortedMap;

import static com.eight.cloud.core.global.constant.NumConstant.MINUTE_SECONDS;
import static com.eight.cloud.core.global.constant.NumConstant.THOUSAND;
import static com.eight.cloud.core.global.constant.StrConstant.*;
import static com.eight.cloud.core.utils.format.ObjectFormat.objectToStr;
import static com.eight.cloud.openapi.common.CacheKeyConstant.OPEN_API_NONCE;

/**
 * 身份认证切面
 *
 * @ClassName: OpenApiAspect
 * @Author: TXC
 * @Date: 2024-11-14 17:42
 **/
@Slf4j
@Aspect
public class OpenApiAspect {
    private final AuthDetailService authDetailService;
    private final LogRecordService logRecordService;
    private final RedisUtils redisUtils;
    private final static Long TIMEOUT = MINUTE_SECONDS;

    public OpenApiAspect(AuthDetailService authDetailService, LogRecordService logRecordService, RedisUtils redisUtils) {
        this.authDetailService = authDetailService;
        this.logRecordService = logRecordService;
        this.redisUtils = redisUtils;
    }


    /**
     * 身份认证和数据校验
     * @param joinPoint ProceedingJoinPoint
     * @return Object
     * @throws Throwable 异常
     */
    @Around("@annotation(com.eight.cloud.openapi.annotations.OpenApi)")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = null;
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        OpenApi methodApiOperation = methodSignature.getMethod().getDeclaredAnnotation(OpenApi.class);
        String body = getSortedArgs(joinPoint.getArgs(), methodSignature.getMethod().getParameterAnnotations());
        Object result;
        try {
            request = HttpContextUtils.getHttpServletRequest();
            // 获取OpenAPI的方法说明@OpenApi
            logRecordService.doBefore(request, methodApiOperation, body, joinPoint.getTarget().getClass());
            this.verifyHeaderParams(request);
            this.verifyTimestamp(request);
            this.verifyNonce(request);
            AppUser appUser = this.checkAuth(request);
            this.verifySignature(appUser.getSecretKey(), request, body);
            assert request != null;
            UserContext.setUser(appUser);
            result = joinPoint.proceed();
            logRecordService.doSuccess(request, methodApiOperation, body, joinPoint.getTarget().getClass());
        }catch (Exception e) {
            logRecordService.doException(request, methodApiOperation, body, joinPoint.getTarget().getClass(), e);
            return Result.fail(e.getMessage());
        }
        UserContext.clear();
        return result;
    }

    /**
     * 校验身份
     */
    private AppUser checkAuth(HttpServletRequest request) {
        if (ObjectUtil.isNotNull(request)) {
            String secretId = request.getHeader(SECRET_ID);
            String secretKey = request.getHeader(SECRET_KEY);
            try {
                secretKey = RSAUtils.decrypt(secretKey);
                AuthDetails authDetails = authDetailService.loadSecretKeyBySecretId(secretId);
                if (ObjectUtil.isNull(authDetails) || ObjectUtil.isNull(authDetails.getUser())) {
                    throw new RuntimeException("认证信息错误");
                }
                AppUser appUser = authDetails.getUser();
                if (ObjectUtil.notEqual(secretKey, RSAUtils.decrypt(appUser.getSecretKey()))) {
                    throw new RuntimeException("secretKey错误");
                }
                appUser.setSecretKey(secretKey);
                return appUser;
            }catch (Exception e) {
                throw new RuntimeException("secretKey错误");
            }
        }
        throw new RuntimeException("系统繁忙，请稍后再试！");
    }

    private String getSortedArgs(Object[] args, Annotation[][] annotations) {
        for (int i = 0; i < args.length; i++) {
            for (int j = 0; j < annotations[i].length; j++) {
                if (annotations[i][j].annotationType().equals(RequestBody.class)) {
                    return objectToStr(ObjectFormat.objToObject(args[i], SortedMap.class));
                }
            }
        }
        return EMPTY;
    }

    /**
     * 校验签名
     */
    private void verifySignature(String secretKey, HttpServletRequest request, String args) {
        if (ObjectUtil.isNotNull(request)) {
            String signature = request.getHeader(SIGN);
            String data = args + request.getHeader(TIMESTAMP) + request.getHeader(NONCE);
            if (!SignatureUtil.verifySignature(secretKey, data, signature)) {
                throw new RuntimeException("签名错误");
            }
        } else {
            throw new RuntimeException("系统繁忙，请稍后再试！");
        }
    }

    /**
     * 校验请求头参数
     */
    private void verifyHeaderParams(HttpServletRequest request) {
        if (ObjectUtil.isNotNull(request)) {
            String sign = request.getHeader(SIGN);
            String secretId = request.getHeader(SECRET_ID);
            String secretKey = request.getHeader(SECRET_KEY);
            String timestamp = request.getHeader(TIMESTAMP);
            String nonce = request.getHeader(NONCE);
            if (ObjectUtil.isEmpty(sign)) {
                throw new ValidationException("请传入签名");
            }
            if (ObjectUtil.isEmpty(secretId)) {
                throw new ValidationException("请传入secretId");
            }
            if (ObjectUtil.isEmpty(secretKey)) {
                throw new ValidationException("请传入secretKey");
            }
            if (ObjectUtil.isEmpty(timestamp)) {
                throw new ValidationException("请传入时间戳");
            }
            if (ObjectUtil.isEmpty(nonce)) {
                throw new ValidationException("请传入随机数");
            }
        } else {
            throw new RuntimeException("系统繁忙，请稍后再试！");
        }
    }

    /**
     * 校验随机数 防重复请求
     * @param request HttpServletRequest
     */
    private void verifyNonce(HttpServletRequest request) {
        if (ObjectUtil.isNotNull(request)) {
            String nonce = request.getHeader(NONCE);
            if (redisUtils.exists(CacheKeyConstant.generateKey(OPEN_API_NONCE, request.getRequestURI().replace(SLASH, DASH), nonce))) {
                throw new ValidationException("请勿重复请求");
            }
            redisUtils.set(CacheKeyConstant.generateKey(OPEN_API_NONCE, request.getRequestURI().replace(SLASH, DASH), nonce), nonce, TIMEOUT);
        }else {
            throw new RuntimeException("系统繁忙，请稍后再试！");
        }
    }

    /**
     * 校验时间戳
     * @param request HttpServletRequest
     */
    private void verifyTimestamp(HttpServletRequest request) {
        if (ObjectUtil.isNotNull(request)) {
            String timestamp = request.getHeader(TIMESTAMP);
            if (ObjectUtil.isNotEmpty(timestamp)) {
                long currentTime = System.currentTimeMillis() / THOUSAND;
                long timeDiff = currentTime - Long.parseLong(timestamp);
                if (timeDiff > TIMEOUT) {
                    throw new RuntimeException("请求过期");
                }
            }
        }else {
            throw new RuntimeException("系统繁忙，请稍后再试！");
        }
    }
}
